﻿namespace EFS.RC.CC.Trackers.Team
{
	/// <summary>
	/// Presences tracker
	/// </summary>
	internal class Presences : EFS.Common.Async.AsyncRoot
	{
		#region Composition

		// async access
		private object syncAccess = new object();

		// team
		private EFS.RC.CC.Presences.Operators.OfficeService.Team operatorsTeam = null;

		// Remote presences
		private Microsoft.Rtc.Collaboration.Presence.RemotePresenceView remotePresenceView = null;

		// RemotePresentitySubscriptionTarget for each agent
		private System.Collections.Generic.List<Microsoft.Rtc.Collaboration.Presence.RemotePresentitySubscriptionTarget> remotePresentitySubscriptionTargetList =
			new System.Collections.Generic.List<Microsoft.Rtc.Collaboration.Presence.RemotePresentitySubscriptionTarget>();

		// Status
		public enum TrackerStatus { Idle, Started, Terminated }

		// Status
		private TrackerStatus status = TrackerStatus.Idle;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="operatorsOfficeServiceEndPointApplication"></param>
		public Presences(EFS.RC.CC.Presences.Operators.OfficeService.Team operatorsTeam)
		{
			// check
			EFS.Common.Verify.Reference(operatorsTeam, "operatorsTeam");

			// end point
			this.operatorsTeam = operatorsTeam;
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Trackers.Presences [{0}]", operatorsTeam.Describe());
		}

		/// Status
		public TrackerStatus Status { get { return status; } }

		/// <summary>
		/// Begin
		/// </summary>
		/// <returns></returns>
		public EFS.Common.Async.AsyncProcess BeginStartup()
		{
			// start
			return BeginAsyncProcess2(Start);
		}

		/// <summary>
		/// Describe this
		/// </summary>
		/// <returns></returns>
		public EFS.Common.Async.AsyncProcess BeginShutdown()
		{
			// end
			return BeginAsyncProcess2(Stop);
		}

		#endregion

		#region Start

		/// <summary>
		/// Start presence view
		/// </summary>
		private void Start()
		{
			// log
			EFS.EventLog.Log.LogInfo("Start", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != TrackerStatus.Idle)
				{
					throw new System.InvalidOperationException("Only Idle Presence Tracker can be started");
				}

				// started
				status = TrackerStatus.Started;
			}

			// start
			StartRemotePresenceView();

			// track deployment changes
			this.operatorsTeam.OnDeploymentLoad += TeamDeploymentLoad;
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Unhandled exception
		/// </summary>
		/// <param name="ex"></param>
		protected override void OnException(System.Exception ex)
		{
			// check
			EFS.Common.Verify.Reference(ex, "ex");

			// log
			EFS.EventLog.Log.LogException(ex, this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(null, "OnException", ex, this); 

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// Update subscribers list
		/// </summary>
		private void TeamDeploymentLoad(EFS.RC.CC.Presences.Operators.OfficeService.Team operatorsTeam)
		{
			// check
			EFS.Common.Verify.Reference(operatorsTeam, "operatorsTeam");

			// check 
			if (!this.operatorsTeam.Equals(operatorsTeam))
			{
				throw new System.InvalidOperationException("TeamDeploymentLoad Team is different");
			}

			// restart
			BeginAsyncProcess2(Restart);
		}

		/// <summary>
		/// Restart presence view
		/// </summary>
		private void Restart()
		{
			// log
			EFS.EventLog.Log.LogInfo("Restart", this);

			// sync access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				if (status != TrackerStatus.Started)
				{
					throw new System.InvalidOperationException("Only Started Presence Tracker can be restarted");
				}

				// started
				status = TrackerStatus.Started;
			}

			// terminate
			TerminateRemotePresenceView();

			// start
			StartRemotePresenceView();
		}

		/// <summary>
		/// Remove all subscriptions
		/// </summary>
		private void StartRemotePresenceView()
		{
			// make new remote presence view
			remotePresenceView = new Microsoft.Rtc.Collaboration.Presence.RemotePresenceView(operatorsTeam.OperatorsOfficeServiceEndPointApplication.CollaborationApplicationEndpoint);

			// make new list
			operatorsTeam.ForEachAgent(AddToSupscribersList);

			// register for presence changed events
			remotePresenceView.PresenceNotificationReceived += OnOperatorPresenceChanged;

			// check
			if (remotePresentitySubscriptionTargetList.Count > 0)
			{
				// handle change for agent status
				remotePresenceView.StartSubscribingToPresentities(remotePresentitySubscriptionTargetList);
			}
		}

		/// <summary>
		/// Terminate
		/// </summary>
		private void TerminateRemotePresenceView()
		{
			// terminate
			remotePresenceView.PresenceNotificationReceived -= OnOperatorPresenceChanged;
			remotePresenceView.BeginTerminate(EndTerminateRemotePresenceView, remotePresenceView);
		}

		/// <summary>
		/// Terminate complete
		/// </summary>
		/// <param name="ar"></param>
		private void EndTerminateRemotePresenceView(System.IAsyncResult ar)
		{
			// log
			EFS.EventLog.Log.LogInfo("EndTerminateRemotePresenceView", this);

			try
			{
				// check
				(ar.AsyncState as Microsoft.Rtc.Collaboration.Presence.RemotePresenceView).EndTerminate(ar);

				// log
				EFS.EventLog.Log.LogInfo("EndTerminateRemotePresenceView SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Update subscribers list
		/// </summary>
		private void AddToSupscribersList(EFS.RC.CC.Presences.Operators.OfficeService.Agent agentPresence)
		{
			remotePresentitySubscriptionTargetList.Add(new Microsoft.Rtc.Collaboration.Presence.RemotePresentitySubscriptionTarget(agentPresence.User.SIP));
		}

		/// <summary>
		/// Determines whether an agent has become available.
		/// </summary>
		private void OnOperatorPresenceChanged(object sender, Microsoft.Rtc.Collaboration.Presence.RemotePresentitiesNotificationEventArgs e)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOperatorPresenceChanged", this);

			try
			{
				// cycle all notifications and handle changes
				foreach (Microsoft.Rtc.Collaboration.Presence.RemotePresentityNotification notification in e.Notifications)
				{
					// can be null
					if (null == notification.AggregatedPresenceState)
					{
						continue;
					}

					// log
					EFS.EventLog.Log.LogInfo(System.String.Format("Agent Presence changed SIP: {0}, STATUS: {1}", notification.PresentityUri, notification.AggregatedPresenceState.AvailabilityValue), this);

					// get agent
					EFS.RC.CC.Presences.Operators.OfficeService.Agent agentPresence = operatorsTeam.GetAgentBySIP(notification.PresentityUri);

					// must be in list, if not, ignore
					if (null != agentPresence)
					{
						// update 
						agentPresence.Update(notification);
					}
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		#endregion Runtime

		#region Stop

		/// <summary>
		/// Update subscribers list
		/// </summary>
		private void Stop()
		{
			// log
			EFS.EventLog.Log.LogInfo("Start", this);

			// lock local acess
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check already terminated
				if (status != TrackerStatus.Terminated)
				{
					// set terinated
					status = TrackerStatus.Terminated;
				}
				else
				{
					// already terminated
					return;
				}
			}

			// track deployment changes
			this.operatorsTeam.OnDeploymentLoad -= TeamDeploymentLoad;

			// terminate
			TerminateRemotePresenceView();
		}

		#endregion
	}
}
